package main;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Scanner;

import javax.swing.JFileChooser;

import jumpInstructions.Jalr;
import jumpInstructions.Jr;
import jumpInstructions.MIPSJumpInstruction;

public class MIPSSimulator {
	// Properties Files
	private static Properties myRecognizedInstructionsSetOne = new Properties();
	private static Properties myRecognizedInstructionsSetTwo = new Properties();

	// Loading State
	private static Scanner myScanner;
	private static JFileChooser myChooser;
	private static File myInputFile;

	// Instruction Lists
	private static List<String> myBinaryStringList;
	private static List<IMIPSInstruction> myInstructions;

	// Jump Variables
	public static int myLabelCounter;
	public static int currentInstructionLocation;
	public static Map<Integer, String> myLabelLocations;

	// Memory Variables
	private static IMIPSInstruction[] myText;
	private static int[] myStatic;
	public static int[] myStack;
	public static String arrayIndentifier;
	
	// Registers
	public static Map<String, Integer> myRegisters;
	public static int PC; // program counter
	private static int HI; // special register used in multiplication
	private static int LO; // special register used in multiplication

	
	public static int addressConversion(int inputAddress){
			int displacement = 0x7fffefff - inputAddress;
			if (inputAddress <= 0x7fffeffc && displacement <= myStack.length) {
				arrayIndentifier = "stack";
				return inputAddress = 0x7fffefff - inputAddress;
			}

			if (inputAddress >= 0x00400000 && inputAddress < 0x10010000) {
				arrayIndentifier = "text";
				return inputAddress = 0x10010000 - inputAddress;
			}

			if (inputAddress >= 0x10010000) {
				arrayIndentifier = "staticData";
				return inputAddress = 0x10010000 - inputAddress;
			}
			return 0;
		}
	
	
	private static void initializeRegisters() {
		myRegisters.put("$zero", 0);
		myRegisters.put("$at", 0);
		for (int i = 2; i < 28; i++) {
			if (i > 1 && i < 4) {
				myRegisters.put("$v" + Integer.toString(i - 2), 0);
			} else if (i > 3 && i < 8) {
				myRegisters.put("$a" + Integer.toString(i - 4), 0);
			} else if (i > 7 && i < 16) {
				myRegisters.put("$t" + Integer.toString(i - 8), 0);
			} else if (i > 15 && i < 24) {
				myRegisters.put("$s" + Integer.toString(i - 16), 0);
			} else if (i > 23 && i < 26) {
				myRegisters.put("$t" + Integer.toString(i - 16), 0);
			} else if (i > 25 && i < 28) {
				myRegisters.put("$k" + Integer.toString(i - 26), 0);
			}
		}
		myRegisters.put("$gp", 0);
		myRegisters.put("$sp", 0x7fffeffc);
		myRegisters.put("$fp", 0);
		myRegisters.put("$ra", 0);
	}

	private static void setup() throws FileNotFoundException, IOException {
		// Load in properties
		myRecognizedInstructionsSetOne.load(new FileInputStream(new File(
				"recognizedInstructionsOne.properties")));

		myRecognizedInstructionsSetTwo.load(new FileInputStream(new File(
				"recognizedInstructionsTwo.properties")));

		// Initialize memory variables
		myText = new IMIPSInstruction[(2 * 1024) / 4];
		myStatic = new int[4 * 1024];
		myStack = new int[2 * 1024];
		myRegisters = new HashMap<String, Integer>();

		// Initialize registers
		MIPSSimulator.initializeRegisters();
		PC = 0;
		HI = 0;
		LO = 0;

		// System.out.print(myRegisters.keySet().toString());

		// Initialize label variables
		currentInstructionLocation = 0;
		myLabelCounter = 0;
		myLabelLocations = new HashMap<Integer, String>();
		

		// Instruction lists
		myBinaryStringList = new ArrayList<String>();
		myInstructions = new ArrayList<IMIPSInstruction>();

		// Choose and read in file
		myChooser = new JFileChooser();
		int retval = myChooser.showOpenDialog(null);
		if (retval == JFileChooser.APPROVE_OPTION) {
			myInputFile = myChooser.getSelectedFile();
			MIPSSimulator.readInFile(myInputFile);
		}
	}

	private static void parseInstructions() throws SecurityException,
			NoSuchMethodException, IllegalArgumentException,
			InstantiationException, IllegalAccessException,
			InvocationTargetException {

		for (int i = 0; i < myBinaryStringList.size(); i++) {
			try {
				if (myBinaryStringList.get(i) != null) {
					if (myBinaryStringList.get(i).substring(0, 6).equals(
							"000001")) {
						if (myBinaryStringList.get(i).substring(11, 16).equals(
								"00000")) {
							Class[] intArgsClass = new Class[] { String.class };
							Class<?> expression = Class
									.forName("jumpInstructions.Bltz");
							Constructor<?> currConstructor = expression
									.getConstructor(intArgsClass);
							IMIPSInstruction currInstruction = (IMIPSInstruction) currConstructor
									.newInstance(myBinaryStringList.get(i));
							myInstructions.add(currInstruction);
						} else {
							Class[] intArgsClass = new Class[] { String.class };
							Class<?> expression = Class
									.forName("jumpInstructions.Bgez");
							Constructor<?> currConstructor = expression
									.getConstructor(intArgsClass);
							IMIPSInstruction currInstruction = (IMIPSInstruction) currConstructor
									.newInstance(myBinaryStringList.get(i));
							myInstructions.add(currInstruction);
						}
					} else if (myBinaryStringList.get(i).substring(0, 6)
							.equals("000000")) {
						String backMarker = myBinaryStringList.get(i)
								.substring(
										myBinaryStringList.get(i).length() - 6,
										myBinaryStringList.get(i).length());

						Class[] intArgsClass = new Class[] { String.class };

						Class<?> expression = Class
								.forName(myRecognizedInstructionsSetOne
										.getProperty(backMarker));
						Constructor<?> currConstructor = expression
								.getConstructor(intArgsClass);
						IMIPSInstruction currInstruction = (IMIPSInstruction) currConstructor
								.newInstance(myBinaryStringList.get(i));
						myInstructions.add(currInstruction);

					} else {
						String frontMarker = myBinaryStringList.get(i)
								.substring(0, 6);
						Class[] intArgsClass = new Class[] { String.class };

						Class<?> expression = Class
								.forName(myRecognizedInstructionsSetTwo
										.getProperty(frontMarker));
						Constructor<?> currConstructor = expression
								.getConstructor(intArgsClass);
						IMIPSInstruction currInstruction = (IMIPSInstruction) currConstructor
								.newInstance(myBinaryStringList.get(i));
						myInstructions.add(currInstruction);
					}
					currentInstructionLocation++;
				} else {
					break;
				}
			} catch (ClassNotFoundException e) {
				System.out.print(myBinaryStringList.get(i)
						+ " is not a valid instruction");
				System.exit(0);
			}
		}
		for (int i = 0; i < myInstructions.size(); i++) {
			myText[i] = myInstructions.get(i);
		}
	}

	public static void readHex() throws IOException {
		File f;
		f = new File("HardTest.txt");
		if (!f.exists() && f.length() < 0)
			System.out
					.println("The specified file is either not exist or doesn't contain data");
		else {
			FileInputStream finp = new FileInputStream(f);
			byte b;
			do {
				b = (byte) finp.read();
				System.out.print((char) b);
			} while (b != -1);
			finp.close();
		}
	}

	public static void readInFile(File inputFile) throws FileNotFoundException {
		myScanner = new Scanner(inputFile);
		int index = 0;
		while (myScanner.hasNext()) {
			String s = myScanner.nextLine();

			if (s.equals("DATA SEGMENT")) {
				break;
			}

			String currString = "";
			for (int i = 2; i < s.length(); i++) {
				int currChar = s.charAt(i);
				if (currChar > 48 && (currChar < 58)) {
					currChar = currChar - 48;
					String tempString = Integer.toBinaryString(currChar);
					String newTempString = "";
					if (tempString.length() != 4) {
						for (int j = 0; j < 4 - tempString.length(); j++) {
							newTempString += "0";
						}
						newTempString += tempString;
						tempString = newTempString;
					}
					currString += tempString;
				} else if (currChar > 96 && currChar < 103) {
					currChar = 9 + currChar - 96;
					String tempString = Integer.toBinaryString(currChar);
					String newTempString = "";
					if (tempString.length() != 4) {
						for (int k = 0; k < 4 - tempString.length(); k++) {
							newTempString += "0";
						}
						newTempString += tempString;
						tempString = newTempString;
					}
					currString += tempString;
				} else if (currChar == 48) {
					currString += "0000";
				}
			}
			myBinaryStringList.add(currString);
			index++;
		}
		int staticIndex = 0;
		while (myScanner.hasNext()) {
			String s = myScanner.nextLine();
			String data = s.substring(13, 21);
			int dataInt = Integer.parseInt(data, 16);
			myStatic[staticIndex] = dataInt;
			staticIndex++;
		}
	}

	public static void print(){
		List<String> myAnswerCopy = new ArrayList<String>();
		for (int i = 0; i < myText.length; i++){
			if(!(myText[i] == null)){
			if(myLabelLocations.containsKey(i)){
			myAnswerCopy.add(myLabelLocations.get(i));	
			myAnswerCopy.add(myText[i].toString());
			} else {
				myAnswerCopy.add(myText[i].toString());
			}
			}
		}
		for (String currString: myAnswerCopy){
			System.out.println(currString);
		}
	}
	
	public static void main(String[] args) throws FileNotFoundException,
			IOException, SecurityException, IllegalArgumentException,
			NoSuchMethodException, InstantiationException,
			IllegalAccessException, InvocationTargetException {
		// MIPSSimulator.readHex();
		MIPSSimulator.setup();
		MIPSSimulator.parseInstructions();

		//MIPSSimulator.print();
		
		/*
		 * List<String> testMyBinaryStringList = new ArrayList<String>(
		 * myBinaryStringList); Collection<IMIPSInstruction> testMyInstructions
		 * = new ArrayList<IMIPSInstruction>( myInstructions);
		 */

		System.out
				.println("Welcome to Patrick Lu's MIPS Simulator.\n\nOptions are: \n"
						+ "run - runs through the whole program\n"
						+ "step - single step mode\n");
		myScanner = new Scanner(System.in);
		String choice = myScanner.nextLine();
		while (!(choice.equals("run") || choice.equals("step"))) {
			System.out.println("Invalid Input");
			choice = myScanner.nextLine();
		}
		if (choice.equals("run")) {
			System.out.println("run to completion selected");
			while (true) { // infinite loop waiting for syscall exit
				if (!myText[PC].equals(null)) {
					myText[PC].execute();
					PC++;
				} else {
					break;
				}
			}
		} else {
			System.out
					.println("step mode selected\n"
							+ "p reg (e.g. p $fp, prints contents in hex of register $fp)\n"
							+ "p all print contents of all registers including PC, HI, & LO\n"
							+ "d addr print contents of memory location addr in hex, assume addr is a word address\n"
							+ "s n execute next n instructions and stop\n");
			while (true) {
				choice = myScanner.nextLine();
				if (choice.equals("p all")) {
					System.out.println("PC: " + Integer.toHexString(PC));
					System.out.println("HI: " + Integer.toHexString(HI));
					System.out.println("LO: " + Integer.toHexString(LO));
					Iterator<String> iterator = myRegisters.keySet().iterator();
					while (iterator.hasNext()) {
						String key = iterator.next().toString();
						String value = Integer
								.toHexString(myRegisters.get(key));
						System.out.println(key + ": " + value);
					}
					System.out.println();
				} else if (choice.substring(0, 1).equals("p")
						&& !choice.substring(2, 5).equals("all")) {
					String register = choice.substring(2, choice.length());
					String value = Integer.toHexString(myRegisters
							.get(register));
					System.out.println(register + ": " + value);
				} else if (choice.substring(0, 1).equals("d")) {
					int address = Integer.parseInt(choice.substring(2, choice.length()));
					address = addressConversion(address);
					if (!(address == 0)){
					if(arrayIndentifier.equals("text")){
						System.out.println(myText[address].toString());
					} else if (arrayIndentifier.equals("stack")){
						System.out.println(myStack[address]);
					} else {
						System.out.println(myStatic[address]);
					}
					} else {
						System.out.println("Invalid address input");
					}
				} else {
					int numberOfSteps = Integer.parseInt(choice.substring(2,
							choice.length()));
					for (int i = 0; i < numberOfSteps; i++) {
						if (!myText[PC].equals(null)) {
							myText[PC].execute();
							System.out.println(myText[PC].toString());
							PC++;
						}
					}
				}
			}
		}
	}
}